﻿#if UNITY_EDITOR && DEBUG_BEHAVIOUR_TREE
#define DEBUG_TREE
#endif
using Devil;
using GameToolkit.BehaviourTree;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

namespace GameToolkit.Editor
{
    public class BehaviourTreeEditor : EditorWindow
    {
        readonly static Type[] sDockTypes = { typeof(BehaviourTreeEditor), typeof(SceneView) };

        [InitializeOnLoadMethodParallel(ExecutionOrder = InitializeOrder.First, IsOnMainThread = true)]
        static void InitializeUtils()
        {
            BehaviourTreeUtils.OpenAsset = (asset, newWin) => OpenAsset(asset, newWin);
            BehaviourTreeUtils.OpenPlayer = (player, newWin) =>
            {
                var win = OpenAsset(player.BindingAsset, newWin);
                if (win != null)
                    win.ResetPlayer(player);
            };
            BehaviourTreeUtils.IsDebugingPlayer = (player) => {
                var array = Resources.FindObjectsOfTypeAll<BehaviourTreeEditor>();
                var len = array == null ? 0 : array.Length;
                for (int i = 0; i < len; i++)
                {
                    var editor = array[i];
                    if (editor.hasFocus)
                        return editor.mPlayer == player;
                }
                return false;
            };
        }

        [MenuItem("Window/Game Toolkit/Behaviour Tree Editor")]
        static void OpenWindow()
        {
            var win = GetWindow<BehaviourTreeEditor>(sDockTypes);
            win.titleContent.image = BehaviourTreeLib.Instance.TreeIcon;
            win.minSize = new Vector2(640, 480);
            if (win.mPingAsset != null)
                win.mPingAsset.SetEnabled(win.behaviourTreeAsset != null);
        }

        [MenuItem("Window/Game Toolkit/Behaviour Tree Editor (New)")]
        static void NewWindow()
        {
            var win = CreateWindow<BehaviourTreeEditor>(sDockTypes);
            win.titleContent.image = BehaviourTreeLib.Instance.TreeIcon;
            win.minSize = new Vector2(640, 480);
            if (win.mPingAsset != null)
                win.mPingAsset.SetEnabled(win.behaviourTreeAsset != null);
        }

#if DEBUG_BEHAVIOUR_TREE
        [MenuItem("CONTEXT/Component/Debug Behaviour Tree")]
        static void DebugAI(MenuCommand cmd)
        {
            var com = cmd.context as Component;
            var go = com == null ? null : com.gameObject;
            if (go != null)
            {
                lock (BehaviourTreePlayer._global_binding_lock)
                {
                    foreach (var player in BehaviourTreePlayer.sAllPlayers)
                    {
                        if (player.Executable != null && player.Executable.gameObject == go)
                        {
                            BehaviourTreeUtils.OpenPlayer?.Invoke(player, false);
                            return;
                        }
                    }
                }
            }
        }
#endif

        public static BehaviourTreePlayer GetPlayerInParent(BehaviourTreePlayer player, BehaviourTreeAsset asset)
        {
            while(player != null)
            {
                if (player.Parent == null || player.BindingAsset == asset)
                    break;
                else
                    player = player.Parent;
            }
            if (player != null)
                return player.FindPlayerForAsset(asset);
            else
                return null;
        }

        public static BehaviourTreeEditor OpenAsset(BehaviourTreeAsset asset, bool newWindow)
        {
            if (asset == null)
                return null;
            var array = Resources.FindObjectsOfTypeAll<BehaviourTreeEditor>();
            var len = array == null ? 0 : array.Length;
            BehaviourTreeEditor focusWin = null;
            for (int i = 0; i < len; i++)
            {
                var editor = array[i];
                if (editor.hasFocus)
                    focusWin = editor;
                if (editor.behaviourTreeAsset == asset)
                {
                    editor.Reopen(asset);
                    editor.Focus();
                    return editor;
                }
                var player = GetPlayerInParent(editor.mPlayer, asset);
                if (player != null)
                {
                    editor.Reopen(asset);
                    editor.ResetPlayer(player);
                    editor.Focus();
                    return editor;
                }
            }
            BehaviourTreeEditor win;
            if (newWindow)
                win = CreateWindow<BehaviourTreeEditor>(sDockTypes);
            else
                win = focusWin == null ? GetWindow<BehaviourTreeEditor>(sDockTypes) : focusWin;
            win.titleContent.image = BehaviourTreeLib.Instance.TreeIcon;
            win.minSize = new Vector2(640, 480);
            if (win.behaviourTreeAsset != null && win.behaviourTreeAsset != asset)
            {
                if (!win.ExecuteClearGraph(true))
                    return null;
            }
            win.Reopen(asset);
            win.Focus();
            return win;
        }

        bool ExecuteClearGraph(bool cancelable)
        {
            if (mGraph != null && mGraph.IsDirty)
            {
                if (cancelable)
                {
                    var action = EditorUtility.DisplayDialogComplex("Open BehaviourTreeAsset", "当前行为树未保存，是否继续？", "保存", "取消", "放弃修改");
                    if (action == 0)
                        mGraph.SaveGraph(false);
                    else if (action == 1)
                        return false;
                }
                else
                {
                    var cancel = EditorUtility.DisplayDialog("Open BehaviourTreeAsset", "当前行为树未保存，是否继续？", "保存", "放弃修改");
                    if (cancel)
                        mGraph.SaveGraph(false);
                }
            }
            return true;
        }

        [SerializeField]
        private VisualTreeAsset m_VisualTreeAsset = default;

        [SerializeField]
        private BehaviourTreeAsset m_BehaviourTreeAsset;
        public BehaviourTreeAsset behaviourTreeAsset
        {
            get
            {
                return m_BehaviourTreeAsset;
            }
            set
            {
                var clean = m_BehaviourTreeAsset == null && ((object)m_BehaviourTreeAsset) != null;
                //var nil = m_BehaviourTreeAsset == null;
                //var nil2 = value == null;
                if (m_BehaviourTreeAsset != value || clean)
                {
                    m_BehaviourTreeAsset = value;
                    titleContent.text = value == null ? "<Behaviour Tree Editor>" : value.Title;
                    if (mGraph != null)
                        mGraph.asset = value;
                    if (mPingAsset != null)
                        mPingAsset.SetEnabled(value != null);
                    var player = value == null ? null : GetPlayerInParent(mPlayer, value);
                    ResetPlayer(player);
                }
            }
        }

        public void Reopen(BehaviourTreeAsset asset)
        {
            var clean = m_BehaviourTreeAsset == null && ((object)m_BehaviourTreeAsset) != null;
            //var nil = m_BehaviourTreeAsset == null;
            //var nil2 = value == null;
            if (m_BehaviourTreeAsset != asset || clean)
            {
                m_BehaviourTreeAsset = asset;
                titleContent.text = asset == null ? "<Behaviour Tree Editor>" : asset.Title;
                if (mGraph != null)
                    mGraph.asset = asset;
                if (mPingAsset != null)
                    mPingAsset.SetEnabled(asset != null);
                var player = asset == null ? null : GetPlayerInParent(mPlayer, asset);
                ResetPlayer(player);
            }
            else if(asset != null)
            {
                OnClickReset();
            }
        }

        public void DebugAsset(BehaviourTreeAsset asset)
        {
            if(mGraph != null && asset != null)
            {
                var player = mPlayer == null ? null : mPlayer.GetRootPlayer();
                player = player == null ? null : player.FindPlayerForAsset(asset);
                if (player == null || !player.IsValid)
                {
#if DEBUG_TREE
                    mGraph.UnbindRuntime();
#endif
                    mGraph.asset = asset;
                }
                else
                {
#if DEBUG_TREE
                    mGraph.BindRuntime(player);
#endif
                }
                SetTreePath(asset, player);
                //SetPlayer(player);
                //if (player == null)
                //    mGraph.asset = asset;
            }
        }

        [SerializeField]
        BehaviourTreeSearchProvidor m_NodeProvidor;
        public BehaviourTreeSearchProvidor NodeProvidor
        {
            get
            {
                if (m_NodeProvidor == null)
                    m_NodeProvidor = ScriptableObject.CreateInstance<BehaviourTreeSearchProvidor>();
                m_NodeProvidor.AsDecoratorTree = false;
                m_NodeProvidor.Graph = mGraph;
                return m_NodeProvidor;
            }
        }

        [SerializeField]
        BehaviourTreeSearchProvidor m_DecoratorProvidor;
        public BehaviourTreeSearchProvidor DecoratorProvidor
        {
            get
            {
                if (m_DecoratorProvidor == null)
                    m_DecoratorProvidor = ScriptableObject.CreateInstance<BehaviourTreeSearchProvidor>();
                m_DecoratorProvidor.AsDecoratorTree = true;
                m_DecoratorProvidor.Graph = mGraph;
                return m_DecoratorProvidor;
            }
        }

        VisualElement mUxmlRoot;
        ToolbarMenu mFileBtn;
        Button mPingAsset;
        BehaviourTreeGraph mGraph;
        Button mSavBtn;
        Button mResetBtn;
        ToolbarBreadcrumbs mTreePath;
        Toggle mLinkTog;

        BehaviourTreePlayer mPlayer;
        internal BehaviourTreeGraph Graph => mGraph;

        void RegistCallbacks()
        {
            if (mGraph != null)
            {
                mGraph.AttachedWindow = this;
                mGraph.asset = behaviourTreeAsset;
                mGraph.InspectorToggle = mUxmlRoot.Q<Toggle>("InspectorTog");
                mGraph.OnIsDirty += OnGraphDirty;
            }
            if (mSavBtn != null)
            {
                mSavBtn.clicked += OnClickSave;
                mSavBtn.SetEnabled(mGraph != null && mGraph.IsDirty);
            }
            if(mResetBtn != null)
            {
                mResetBtn.clicked += OnClickReset;
                //mResetBtn.SetEnabled(mGraph != null && mGraph.IsDirty);
            }
            if(mPingAsset != null)
            {
                mPingAsset.clicked += OnClickPingAsset;
                mPingAsset.SetEnabled(behaviourTreeAsset != null);
            }
            
            if(mLinkTog != null)
            {
                mLinkTog.value = EditorPrefs.GetBool("LinkBehaviourTree", true);
                mLinkTog.RegisterValueChangedCallback(OnToggleLink);
            }
            RebuildFileMenu();
#if DEBUG_TREE
            OnSelectionChanged(true);
#endif
        }

        void UnregistCallbacks()
        {
            if (mFileBtn != null)
            {
                mFileBtn.menu.MenuItems().Clear();
            }
            if (mSavBtn != null)
            {
                mSavBtn.clicked -= OnClickSave;
            }
            if(mResetBtn != null)
            {
                mResetBtn.clicked -= OnClickReset;
            }
            if(mPingAsset != null)
            {
                mPingAsset.clicked -= OnClickPingAsset;
            }
            if (mGraph != null)
            {
                mGraph.OnIsDirty -= OnGraphDirty;
#if DEBUG_TREE
                mGraph.UnbindRuntime();
#endif
            }
            if(mLinkTog != null)
            {
                EditorPrefs.SetBool("LinkBehaviourTree", mLinkTog.value);
                mLinkTog.UnregisterValueChangedCallback(OnToggleLink);
            }
        }

        void RebuildFileMenu()
        {
            if (mFileBtn != null)
            {
                mFileBtn.menu.MenuItems().Clear();
                Func<DropdownMenuAction, DropdownMenuAction.Status> btnStat = (menu) => behaviourTreeAsset == null || mPlayer != null ? DropdownMenuAction.Status.Disabled : DropdownMenuAction.Status.Normal;
                Func<DropdownMenuAction, DropdownMenuAction.Status> btnStat2 = (menu) => mPlayer == null ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled;
                mFileBtn.menu.AppendAction("Reset", (evt) => OnClickReset(), DropdownMenuAction.Status.Normal);
                mFileBtn.menu.AppendAction("New Behaviuor Tree", (evt) => behaviourTreeAsset = null, btnStat2);
                mFileBtn.menu.AppendSeparator();
                mFileBtn.menu.AppendAction("Save", (evt) => mGraph?.SaveGraph(false), btnStat2);
                mFileBtn.menu.AppendAction("Save As", (evt) => mGraph?.SaveGraph(true), btnStat2);
            }
        }

        private void OnEnable()
        {
            if (m_NodeProvidor == null)
                m_NodeProvidor = ScriptableObject.CreateInstance<BehaviourTreeSearchProvidor>();
            if (m_DecoratorProvidor == null)
                m_DecoratorProvidor = ScriptableObject.CreateInstance<BehaviourTreeSearchProvidor>();

            if (m_BehaviourTreeAsset == null)
                titleContent.text = "BehaviourTree Editor";
            else if (mGraph != null && mGraph.IsDirty)
                titleContent.text = $"{m_BehaviourTreeAsset.name} *";
            else
                titleContent.text = m_BehaviourTreeAsset.name;

            Selection.selectionChanged += OnSelectionChanged;
            OnSelectionChanged();
        }

        private void OnDisable()
        {
            ExecuteClearGraph(false);
            Selection.selectionChanged -= OnSelectionChanged;
            ResetPlayer(null);
            UnregistCallbacks();
        }

        private void OnDestroy()
        {
            if (mGraph != null)
            {
                mGraph.Dispose();
            }
        }

        void CreateGUI()
        {
            UnregistCallbacks();
            // Each editor window contains a root VisualElement object
            VisualElement root = rootVisualElement;

            // VisualElements objects can contain other VisualElement following a tree hierarchy.
            //VisualElement label = new Label("Hello World! From C#");
            //root.Add(label);

            // Instantiate UXML
            VisualElement content = m_VisualTreeAsset.Instantiate();
            root.Add(content);
            content.StretchToParentSize();

            mUxmlRoot = content;

            mLinkTog = content.Q<Toggle>("linkTog");
            mTreePath = content.Q<ToolbarBreadcrumbs>("treePath");
            mGraph = content.Q("BehaviourTreeGraph") as BehaviourTreeGraph;
            mPingAsset = content.Q<Button>("PingAsset");
            mSavBtn = content.Q<Button>("Save");
            mResetBtn = content.Q<Button>("Reset");
            mFileBtn = content.Q<ToolbarMenu>("File");

            RegistCallbacks();

        }

        private void OnToggleLink(ChangeEvent<bool> evt)
        {
            if (evt.newValue)
                OnSelectionChanged();
        }

        void OnSelectionChanged()
        {
#if DEBUG_TREE
            OnSelectionChanged(false);
#endif
        }

#if DEBUG_TREE
        private void OnSelectionChanged(bool removable)
        {
            if(mLinkTog != null && mLinkTog.value && mGraph != null) 
            {
                var obj = Selection.activeGameObject;
                BehaviourTreePlayer player = null;
                lock (BehaviourTreePlayer._global_binding_lock)
                {
                    foreach (var it in BehaviourTreePlayer.sAllPlayers)
                    {
                        if (it.BindingAsset == behaviourTreeAsset && it.Executable.IsValidInstance() && it.Executable.gameObject == obj)
                        {
                            player = it;
                            break;
                        }
                    }
                }
                //var sel = Selection.activeGameObject?.GetComponent<IBehaviourTreeBinder>();
                //BehaviourTreePlayer player;
                //if (sel == null)
                //    player = null;
                //else if (behaviourTreeAsset == null)
                //    player = sel.GetPlayerForAsset(sel.behaviourTreeAsset);
                //else
                //    player = sel.GetPlayerForAsset(behaviourTreeAsset);
                if (removable || player != null)
                    ResetPlayer(player);
            }
        }
#endif

        public void ResetPlayer(BehaviourTreePlayer player)
        {
            var v = player != null && player.IsValid ? player : null;
            if (mPlayer != v)
            {
#if DEBUG_TREE
                if (mPlayer != null)
                    mPlayer.OnPlayerDestroied -= OnPlayerDestroied;
#endif
                mPlayer = v;
#if DEBUG_TREE
                if (mPlayer != null)
                    mPlayer.OnPlayerDestroied += OnPlayerDestroied;
#endif
            }
            SetTreePath(behaviourTreeAsset, mPlayer);
            if (mGraph != null)
            {
                if (v != null)
                {
                    //mGraph.asset = v.BindingAsset;
#if DEBUG_TREE
                    mGraph.BindRuntime(player);
#endif
                }
                else
                {
#if DEBUG_TREE
                    mGraph.UnbindRuntime();
#endif
                    mGraph.asset = behaviourTreeAsset;
                }
            }
        }

        void OnPlayerDestroied(BehaviourTreePlayer player)
        {
            ResetPlayer(null);
            //if(mGraph != null)
            //{
            //    mGraph.UnbindRuntime();
            //}
        }

        void SetTreePath(BehaviourTreeAsset asset, BehaviourTreePlayer player)
        {
            if (mTreePath == null)
                return;
            try
            {
                while(mTreePath.childCount > 0)
                {
                    mTreePath.PopItem();
                }
            }
            finally
            {
                if (player != null || mPlayer != null)
                {
                    var active = player == null ? mPlayer : player;
                    var go = active.Executable.IsValidInstance() ? active.Executable.gameObject : null;
                    if (go != null)
                        mTreePath.PushItem(go.name, () => EditorGUIUtility.PingObject(go));
                    else if (active.Executable != null)
                        mTreePath.PushItem($"[{active.Executable}]");
                    else
                        mTreePath.PushItem("[Player]");
                    bool containsThis = false;
                    var lst = new List<BehaviourTreePlayer>();
                    var current = player;
                    while (current != null)
                    {
                        lst.Add(current);
                        if (current.BindingAsset == behaviourTreeAsset)
                            containsThis = true;
                        current = current.Parent;
                    }
                    if (!containsThis && behaviourTreeAsset != null)
                    {
                        var item = mPlayer;
                        var tree = behaviourTreeAsset;
                        Action action = () =>
                        {
                            if (mGraph != null)
                            {
                                if (item != null)
                                {
#if DEBUG_TREE
                                    mGraph.BindRuntime(item);
#endif
                                }
                                else
                                {
#if DEBUG_TREE
                                    mGraph.UnbindRuntime();
#endif
                                    mGraph.asset = tree;
                                }
                            }
                        };
                        mTreePath.PushItem($"This: {tree.name}", action);
                    }

                    if (asset != null && player == null)
                    {
                        mTreePath.PushItem($"Debug: {asset.name}", () => EditorGUIUtility.PingObject(asset));
                    }

                    for (int i = lst.Count - 1; i >= 0; i--)
                    {
                        var item = lst[i];
                        var tree = item.BindingAsset;
                        Action action;
                        action = () =>
                        {
                            if (mGraph != null)
                            {
                                if (item != null && item.IsValid)
                                {
#if DEBUG_TREE
                                    mGraph.BindRuntime(item);
#endif
                                }
                                else
                                {
#if DEBUG_TREE
                                    mGraph.UnbindRuntime();
#endif
                                    mGraph.asset = tree;
                                }
                            }
                        };
                        mTreePath.PushItem(item.ToString(), action);
                    }
                }
            }
        }

        private void OnGraphDirty(BehaviourTreeGraph graph)
        {
            if (m_BehaviourTreeAsset != null)
            {
                if (graph.IsDirty)
                    titleContent.text = $"{m_BehaviourTreeAsset.name} *";
                else
                    titleContent.text = m_BehaviourTreeAsset.name;
            }
            if (mSavBtn != null)
            {
                mSavBtn.SetEnabled(graph.IsDirty);
            }
            //if(mResetBtn != null)
            //{
            //    mResetBtn.SetEnabled(graph.IsDirty);
            //}
        }

        private void OnClickPingAsset()
        {
            if (behaviourTreeAsset != null)
                EditorGUIUtility.PingObject(behaviourTreeAsset);
        }

        private void OnClickSave()
        {
            if (mGraph != null)
            {
                mGraph.SaveGraph(false);
                //Debug.Log("Save Data");
            }
        }

        private void OnClickReset()
        {
            if(mGraph != null /*&& m_BehaviourTreeAsset != null*/)
            {
                var path = AssetDatabase.GetAssetPath(m_BehaviourTreeAsset);
                if (!string.IsNullOrEmpty(path))
                    AssetDatabase.ImportAsset(path);
                var player = mGraph.BindingPlayer;
#if DEBUG_TREE
                mGraph.UnbindRuntime();
#endif
                mGraph.LoadGraphNodes();
#if DEBUG_TREE
                if (player != null)
                    mGraph.BindRuntime(player);
#endif
            }
        }

        private void OnGUI()
        {
            if (mGraph != null && Event.current.type == EventType.Layout)
            {
                
                mGraph.Relayout();
            }
            //if (mPlayer != null && mGraph != null)
            //{
            //    mGraph.MarkDirtyRepaint();
            //}
        }
    }

}